﻿using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenInjection;
using MugenInjection.Exceptions;
using MugenInjection.Infrastructure;
using MugenInjectionTest.Bindings;
using Should;

namespace MugenInjectionTest.Converters
{
    [TestClass]
    public class DecoratorDynamicConverterTest : TestBase
    {
        [TestMethod]
        public void BindAsDecoratorForAllTest()
        {
            var value = new SimpleClass();
            var value2 = new SimpleClass();
            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<IDecorator>().ToConstant(value);
                injector.BindAsDecoratorForAll<IDecorator>((context, @class) =>
                                                               {
                                                                   @class.ShouldEqual(value);
                                                                   return new Decorator(value2);
                                                               });

                var adapter = injector.Get<IDecorator>();
                adapter.Simple.ShouldEqual(value2);
            }
        }

        [TestMethod]
        public void BindAsDecoratorForAllDuplicateShouldThrowErrorTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.BindAsDecoratorForAll<IDecorator>((context, @class) => new Decorator(@class));
                injector.BindAsDecoratorForAll<IDecorator>((context, @class) => new Decorator(null));
                TestUtils.Catch<ConflictBindingException>(() => injector.Get<IDecorator>());
            }
        }

        [TestMethod]
        public void UnbindAllDecoratorsForTest()
        {
            var value = new SimpleClass();
            var value2 = new SimpleClass();
            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<IDecorator>().ToConstant(value);
                injector.BindAsDecoratorForAll<IDecorator>((context, @class) =>
                                                               {
                                                                   @class.ShouldEqual(value);
                                                                   return new Decorator(value2);
                                                               });

                var decorator = injector.Get<IDecorator>();
                decorator.Simple.ShouldEqual(value2);

                injector.UnbindAllDecoratorsFor<IDecorator>().ShouldBeTrue();
                injector.Get<IDecorator>().ShouldEqual(value);
            }
        }

        [TestMethod]
        public void UnbindAllDecoratorsForWithConditionTest()
        {
            const string name1 = "name1";
            const string name2 = "name2";
            var value = new SimpleClass();
            var value2 = new SimpleClass();
            var value3 = new SimpleClass();
            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<IDecorator>().ToConstant(value).NamedBinding(name1);
                injector.Bind<IDecorator>().ToConstant(value).NamedBinding(name2);
                injector.BindAsDecoratorFor<IDecorator>(builder => builder.Named(name1),
                                                        (context, @class) => new Decorator(value2));
                injector.BindAsDecoratorFor<IDecorator>(builder => builder.Named(name2),
                                                        (context, @class) => new Decorator(value3));

                var decorator = injector.Get<IDecorator>(name1);
                decorator.Simple.ShouldEqual(value2);

                decorator = injector.Get<IDecorator>(name2);
                decorator.Simple.ShouldEqual(value3);

                injector.UnbindAllDecoratorsFor<IDecorator>().ShouldBeTrue();
                injector.Get<IDecorator>(name1).ShouldEqual(value);
                injector.Get<IDecorator>(name2).ShouldEqual(value);
            }
        }

        [TestMethod]
        public void BindAsDecoratorForNamedTest()
        {
            const string name = "name";
            var value = new SimpleClass();
            var value2 = new SimpleClass();
            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<IDecorator>().ToConstant(value).NamedBinding(name);
                injector.BindAsDecoratorFor<IDecorator>(builder => builder.Named(name),
                                                        (context, @class) =>
                                                        {
                                                            @class.ShouldEqual(value);
                                                            return new Decorator(value2);
                                                        });

                var decorator = injector.Get<IDecorator>(name);
                decorator.Simple.ShouldEqual(value2);
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IDecorator>());

                var dictionary = new Dictionary<string, object>();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, name);
                injector.UnbindDecoratorsFor<IDecorator>(null, null, dictionary,
                                                         InjectorUtilsInternal.EmptyInjectionParams).ShouldBeTrue();
                injector.Get<IDecorator>(name).ShouldEqual(value);
            }
        }

        [TestMethod]
        public void BindAsDecoratorForWhenIntoTest()
        {
            const string name = "name";
            var value = new SimpleClass();
            var value2 = new SimpleClass();

            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<IDecorator>().ToConstant(value).WhenInto(TypeInto.AsType());
                injector.BindAsDecoratorFor<IDecorator>(builder => builder.WhenInto(TypeInto.AsType()),
                                                        (context, @class) =>
                                                        {
                                                            @class.ShouldEqual(value);
                                                            return new Decorator(value2);
                                                        });

                var decorator = injector.GetInto<IDecorator>(TypeInto);
                decorator.Simple.ShouldEqual(value2);
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IDecorator>());

                //Named
                injector.Bind<IDecorator>().ToConstant(value2).WhenInto(TypeInto.AsType()).NamedBinding(name);
                injector.BindAsDecoratorFor<IDecorator>(builder => builder.WhenInto(TypeInto.AsType()).Named(name),
                                                        (context, @class) =>
                                                        {
                                                            @class.ShouldEqual(value2);
                                                            return new Decorator(value);
                                                        });

                decorator = injector.GetInto<IDecorator>(name, TypeInto);
                decorator.Simple.ShouldEqual(value);
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IDecorator>());


                //Unbind
                injector.UnbindDecoratorsFor<IDecorator>(TypeInto, null,
                                                         InjectorUtilsInternal.ReadOnlyEmptyDictionary,
                                                         InjectorUtilsInternal.EmptyInjectionParams);
                injector.GetInto<IDecorator>(TypeInto).ShouldEqual(value);

                //Unbind named
                var dictionary = new Dictionary<string, object>();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, name);
                injector.UnbindDecoratorsFor<IDecorator>(TypeInto, null,
                                                         dictionary,
                                                         InjectorUtilsInternal.EmptyInjectionParams);
                injector.GetInto<IDecorator>(name, TypeInto).ShouldEqual(value2);
            }
        }

        [TestMethod]
        public void BindAsDecoratorForWhenIntoIsAssignableTest()
        {
            var typeInto = typeof(MemberInfo).GetTypeInfo();
            const string name = "name";
            var value = new SimpleClass();
            var value2 = new SimpleClass();


            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<IDecorator>().ToConstant(value).WhenIntoIsAssignable(typeInto.AsType());
                injector.BindAsDecoratorFor<IDecorator>(builder => builder.WhenIntoIsAssignable(typeInto.AsType()),
                                                        (context, @class) =>
                                                        {
                                                            @class.ShouldEqual(value);
                                                            return new Decorator(value2);
                                                        });

                var decorator = injector.GetInto<IDecorator>(typeof(PropertyInfo).GetTypeInfo());
                decorator.Simple.ShouldEqual(value2);
                TestUtils.Catch<BindingNotFoundException>(() => decorator = injector.GetInto<IDecorator>(typeof(object).GetTypeInfo()));

                //Named
                injector.Bind<IDecorator>().ToConstant(value2).WhenIntoIsAssignable(typeInto.AsType()).NamedBinding(name);
                injector.BindAsDecoratorFor<IDecorator>(builder => builder.WhenIntoIsAssignable(typeInto.AsType()).Named(name),
                                                        (context, @class) =>
                                                        {
                                                            @class.ShouldEqual(value2);
                                                            return new Decorator(value);
                                                        });

                decorator = injector.GetInto<IDecorator>(name, typeof(MethodInfo).GetTypeInfo());
                decorator.Simple.ShouldEqual(value);
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IDecorator>());

                //Unbind
                injector.UnbindDecoratorsFor<IDecorator>(typeInto, null,
                                                         InjectorUtilsInternal.ReadOnlyEmptyDictionary,
                                                         InjectorUtilsInternal.EmptyInjectionParams);

                injector.GetInto<IDecorator>(typeof(PropertyInfo).GetTypeInfo()).ShouldEqual(value);

                //Unbind named
                var dictionary = new Dictionary<string, object>();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, name);
                injector.UnbindDecoratorsFor<IDecorator>(typeInto, null,
                                                         dictionary,
                                                         InjectorUtilsInternal.EmptyInjectionParams);
                injector.GetInto<IDecorator>(name, typeof(MethodInfo).GetTypeInfo()).ShouldEqual(value2);
            }
        }

        [TestMethod]
        public void BindAsDecoratorForWhenClassHasAttributeTest()
        {
            Type attribute = typeof(SimpleAttribute);
            var typeInto = typeof(Decorator).GetTypeInfo();
            const string name = "name";
            var value = new SimpleClass();
            var value2 = new SimpleClass();


            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<IDecorator>().ToConstant(value).WhenClassHasAttribute(attribute);
                injector.BindAsDecoratorFor<IDecorator>(builder => builder.WhenClassHasAttribute(attribute),
                                                        (context, @class) =>
                                                        {
                                                            @class.ShouldEqual(value);
                                                            return new Decorator(value2);
                                                        });

                var decorator = injector.GetInto<IDecorator>(typeInto);
                decorator.Simple.ShouldEqual(value2);
                TestUtils.Catch<BindingNotFoundException>(() => decorator = injector.GetInto<IDecorator>(typeof(object).GetTypeInfo()));

                //Named
                injector.Bind<IDecorator>().ToConstant(value2).WhenClassHasAttribute(attribute).NamedBinding(name);
                injector.BindAsDecoratorFor<IDecorator>(builder => builder.WhenClassHasAttribute(attribute).Named(name),
                                                        (context, @class) =>
                                                        {
                                                            @class.ShouldEqual(value2);
                                                            return new Decorator(value);
                                                        });

                decorator = injector.GetInto<IDecorator>(name, typeInto);
                decorator.Simple.ShouldEqual(value);
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IDecorator>());

                //Unbind
                injector.UnbindDecoratorsFor<IDecorator>(typeInto, null,
                                                         InjectorUtilsInternal.ReadOnlyEmptyDictionary,
                                                         InjectorUtilsInternal.EmptyInjectionParams);
                injector.GetInto<IDecorator>(typeInto).ShouldEqual(value);

                //Unbind named
                var dictionary = new Dictionary<string, object>();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, name);
                injector.UnbindDecoratorsFor<IDecorator>(typeInto, null,
                                                         dictionary,
                                                         InjectorUtilsInternal.EmptyInjectionParams);
                injector.GetInto<IDecorator>(name, typeInto).ShouldEqual(value2);
            }
        }

        [TestMethod]
        public void BindAsDecoratorForWhenNamespaceEqualTest()
        {
            var typeInto = typeof(Decorator).GetTypeInfo();
            string ns = typeInto.Namespace;

            const string name = "name";
            var value = new SimpleClass();
            var value2 = new SimpleClass();


            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<IDecorator>().ToConstant(value).WhenNamespaceEqual(ns);
                injector.BindAsDecoratorFor<IDecorator>(builder => builder.WhenNamespaceEqual(ns),
                                                        (context, @class) =>
                                                        {
                                                            @class.ShouldEqual(value);
                                                            return new Decorator(value2);
                                                        });

                var decorator = injector.GetInto<IDecorator>(typeInto);
                decorator.Simple.ShouldEqual(value2);
                TestUtils.Catch<BindingNotFoundException>(() => decorator = injector.GetInto<IDecorator>(typeof(object).GetTypeInfo()));

                //Named
                injector.Bind<IDecorator>().ToConstant(value2).WhenNamespaceEqual(ns).NamedBinding(name);
                injector.BindAsDecoratorFor<IDecorator>(builder => builder.WhenNamespaceEqual(ns).Named(name),
                                                        (context, @class) =>
                                                        {
                                                            @class.ShouldEqual(value2);
                                                            return new Decorator(value);
                                                        });

                decorator = injector.GetInto<IDecorator>(name, typeInto);
                decorator.Simple.ShouldEqual(value);
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IDecorator>());

                //Unbind
                injector.UnbindDecoratorsFor<IDecorator>(typeInto, null,
                                                         InjectorUtilsInternal.ReadOnlyEmptyDictionary,
                                                         InjectorUtilsInternal.EmptyInjectionParams);
                injector.GetInto<IDecorator>(typeInto).ShouldEqual(value);

                //Unbind named
                var dictionary = new Dictionary<string, object>();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, name);
                injector.UnbindDecoratorsFor<IDecorator>(typeInto, null,
                                                         dictionary,
                                                         InjectorUtilsInternal.EmptyInjectionParams);
                injector.GetInto<IDecorator>(name, typeInto).ShouldEqual(value2);
            }
        }

        #region Nested type:Decorator

        [Simple]
        public class Decorator : IDecorator
        {
            /// <summary>
            ///     Initializes a new instance of the <see cref="T:System.Object" /> class.
            /// </summary>
            public Decorator(IDecorator simpleClass)
            {
                Simple = simpleClass;
            }

            public IDecorator Simple { get; set; }
        }

        public interface IDecorator
        {
            IDecorator Simple { get; }
        }

        #endregion

        #region Nested type:SimpleClass

        public class SimpleClass : IDecorator
        {
            #region Implementation of IDecorator

            public IDecorator Simple { get; private set; }

            #endregion
        }

        #endregion
    }
}